﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DynCMS.NET.Core.Entities;
using DynCMS.NET.Core.Entities.Base;
using DynCMS.NET.Core.Interfaces;
using DynCMS.NET.DataAccess.Implementation;

namespace DynCMS.NET.DataAccess.Repos
{
    public abstract class GenericRepository<TEntity> : IDataRepository<TEntity> where TEntity : PersistableEntity
    {
        protected DataContext context;
        protected System.Data.Entity.DbSet<TEntity> table;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:GenericRepository"/> class.
        /// </summary>
        public GenericRepository(DataContext context)
        {
            this.context = context;
            table = this.context.Set<TEntity>();
        }

        /// <summary>
        /// If set to true, repository will not lazy load related entities
        /// </summary>
        public virtual bool ReturnPocoObjects
        {
            get
            {
                return !this.context.Configuration.ProxyCreationEnabled;
            }
            set
            {
                this.context.Configuration.ProxyCreationEnabled = !value;
            }
        }

        /// <summary>
        /// Gets all.
        /// </summary>
        /// <returns></returns>
        public virtual IQueryable<TEntity> GetAll()
        {
            return table;
        }

        /// <summary>
        /// Gets all.
        /// </summary>
        /// <param name="activeOnly">if set to <c>true</c> [active only].</param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> GetAll(bool activeOnly)
        {
            return this.context.Set<TEntity>();//.Where(ent => !activeOnly || !ent.IsDeleted);
        }

        /// <summary>
        /// Gets the specified id.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public abstract TEntity Get(TEntity key);

        /// <summary>
        /// Creates the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public virtual TEntity Create(TEntity entity)
        {
            table.Add(entity);
            SetSystemProperties(entity);
            this.SaveChanges();
            // TODO: add versioning information?
            //this.SaveChanges();
            return entity;
        }

        /// <summary>
        /// Updates the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public virtual TEntity Update(TEntity entity)
        {
            var entityToUpdate = entity;
            if (this.context.Entry(entityToUpdate).State == System.Data.EntityState.Detached)
            {
                entityToUpdate = this.Get(entity);
            }
            // TODO: Check if is deleted when versioning is added
            //if (entityToUpdate.IsDeleted)
            //{
            //    throw new InvalidOperationException("Cannot update deleted object");
            //}

            this.context.Entry(entityToUpdate).CurrentValues.SetValues(entity);

            this.SaveChanges();
            return entity;

            // This code creates updated copy, use if versioning enabled:
            /*
            TEntity updatedEntity = table.Create();
            table.Add(updatedEntity);
            this.context.Entry(updatedEntity).CurrentValues.SetValues(entity);
            //updatedEntity.InformationID = entity.InformationID;
            SetSystemProperties(updatedEntity);

            this.context.Entry(entityToUpdate).Reload();
            //entityToUpdate.IsDeleted = true;
            //entityToUpdate.ModifiedBy = updatedEntity.CreatedBy;
            //entityToUpdate.Updated = updatedEntity.Created;
            //SaveChanges();
            return updatedEntity;
             */
        }

        public virtual TEntity CreateOrUpdate(TEntity entity)
        {
            if (this.Get(entity) == null)
            {
                return this.Create(entity);
            }
            else
            {
                return this.Update(entity);
            }
        }

        protected virtual void SetSystemProperties(TEntity updatedEntity)
        {
            //updatedEntity.Created = DateTime.Now;
            //updatedEntity.Updated = updatedEntity.Created;
            //updatedEntity.CreatedBy = System.Threading.Thread.CurrentPrincipal.Identity.Name;
            //updatedEntity.ModifiedBy = updatedEntity.CreatedBy;
            //updatedEntity.IsDeleted = false;
        }

        /// <summary>
        /// Deletes the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public virtual void Delete(TEntity entity)
        {
            this.context.Set<TEntity>().Remove(entity);
            // TODO: versioned version
            /*
            if (!table.Contains(entity))
            {
                entity = Get(entity);
            }
            entity.IsDeleted = true;
            */
            //return entity;
        }

        /// <summary>
        /// Saves the changes.
        /// </summary>
        /// <returns></returns>
        public virtual int SaveChanges()
        {
            try
            {
                return this.context.SaveChanges();
            }
            catch (System.Data.Entity.Validation.DbEntityValidationException ex)
            {
                //var exception = new WCValidationException("Validation exception", ex);
                foreach (var item in ex.EntityValidationErrors)
                {
                    if (item.IsValid)
                    {
                        continue;
                    }
                    foreach (var err in item.ValidationErrors)
                    {
                        //exception.ValidationErrors.Add(
                        //    new WCValidationException.ValidationError(err.PropertyName, err.ErrorMessage));
                    }
                }
                throw ex;
            }
            catch (Exception ex)
            {
                // log error?
                //return 0;
                throw ex;
            }
        }
    }
}